Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

 <<   zurück
Visual Basic 2005 von Andreas Kühnel
Das umfassende Handbuch
Buch: Visual Basic 2005

Visual Basic 2005
1.233 S., mit 2 CDs, 59,90 Euro
Galileo Computing
ISBN 3-89842-585-1
gp Kapitel 7 Weitere Möglichkeiten von Visual Basic 2005
  gp 7.1 Operatorüberladung
    gp 7.1.1 Die Syntax der Operatorüberladung
    gp 7.1.2 Beispiel einer Operatorüberladung
    gp 7.1.3 Überladung der Operatoren »True« und »False«
    gp 7.1.4 Benutzerdefinierte Konvertierungen
  gp 7.2 Collections (Auflistungen)
    gp 7.2.1 Die elementaren Schnittstellen der Auflistungsklassen
    gp 7.2.2 Die Schnittstelle »IList«
    gp 7.2.3 Die Klasse »ArrayList«
    gp 7.2.4 Das Sortieren der Elemente einer »ArrayList«
    gp 7.2.5 Die Schnittstelle »IDictionary«
    gp 7.2.6 Die Klasse »Hashtable«
    gp 7.2.7 Die Klassen »Queue« und »Stack«
    gp 7.2.8 Objektauflistungen im Überblick
    gp 7.2.9 Benutzerdefinierte Auflistungen
  gp 7.3 Generics
    gp 7.3.1 Ein paar allgemeine Worte
    gp 7.3.2 Die Typproblematik am Beispiel der Klasse »Stack«
    gp 7.3.3 Die Lösung mit einer generischen Klasse
    gp 7.3.4 Typparameter mit Constraints einschränken
    gp 7.3.5 Generische Methoden
    gp 7.3.6 Generics und Vererbung
    gp 7.3.7 Generische Klassen in der .NET-Klassenbibliothek
  gp 7.4 Fortgeschrittene Delegat-Techniken
    gp 7.4.1 Eine Beispielanwendung
    gp 7.4.2 Multicast-Delegaten
  gp 7.5 Attribute
    gp 7.5.1 Das »Flags«-Attribut
    gp 7.5.2 Anmerkungen zu den Attributen
    gp 7.5.3 Benutzerdefinierte Attribute


Galileo Computing

7.2 Collections (Auflistungen)  downtop

Ein wesentliches charakteristisches Merkmal der Arrays ist die freie Verfügbarkeit ihrer Indizes. Sie können ein Element einem Array an einer x-beliebigen Position hinzufügen – unabhängig davon, ob der Index bereits von einem anderen Element belegt ist oder nicht. Wird ein Element aus einem Array entfernt, bleibt ein unbesetzter Index zurück. Ein Array ist somit ein statischer Pool freier und belegter Elementpositionen ohne die Fähigkeit, sich an Änderungen dynamisch anpassen zu können.

An dieser Stelle treten Klassen in Erscheinung, die – ähnlich den Arrays – als Container meist typgleicher Elemente dienen. Im Unterschied zu den herkömmlichen Arrays arbeiten diese Klassen jedoch dynamisch: Sie enthalten keine unbelegten Indizes, sondern vergrößern oder verkleinern ihre Kapazität entsprechend der Anzahl der Einträge. Ganz allgemein werden diese Klassen als Collections oder auch Auflistungen bezeichnet. Jede Klasse unterscheidet sich von der anderen durch besondere Fähigkeiten – sei es die interne Verwaltung der Objekte, der Zugriff auf die Einträge oder die Geschwindigkeit, mit der innerhalb einer Liste nach einem bestimmten Eintrag gesucht werden kann.

Damit Sie die Namen der wichtigsten Auflistungen, mit denen wir uns auch auf den folgenden Seiten beschäftigen werden, schon einmal gehört haben, seien sie hier aufgeführt: ArrayList, Hashtable, Queue und Stack.

Diese Klassen, die alle zum Namespace System.Collections gehören, unterscheiden sich in den Methoden, mit denen der Zugriff auf die Elemente erfolgt, wie die Elemente im Speicher verwaltet werden und welche Operationen sich darauf ausführen lassen. Jede Klasse hat ihre eigene Charakteristik.


Galileo Computing

7.2.1 Die elementaren Schnittstellen der Auflistungsklassen  downtop

Die Grundfunktionalität aller Auflistungen lässt sich auf elementare Methoden zurückführen. Es ist deshalb nicht verwunderlich, dass die Gemeinsamkeiten durch Schnittstellen beschrieben werden, die von den Klassen implementiert werden. Dabei handelt es sich um die folgenden:

gp  IEnumerable
gp  ICollection
gp  IDictionary
gp  IList

Diese Schnittstellen bilden eine Hierarchie, deren Wurzel IEnumerable ist, aus der ICollection abgeleitet wird. Beide Schnittstellen sind charakteristisch für Auflistungsklassen, denn sie stellen die wichtigsten Grundfunktionalitäten bereit. IDictionary und IList leiten sich zudem aus ICollection ab und spalten die Auflistungsklassen in zwei Gruppen:

1. Klassen, die das Interface IList implementieren, beschreiben Objektauflistungen, auf deren Einträge über einen Index zugegriffen wird.
       
2. Klassen, die das Interface IDictionary implementieren, verwalten ihre Einträge über eine Schlüssel-Wert-Kombination.
       

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 7.1     Die Schnittstellen der Auflistungsklassen

Die Schnittstelle »IEnumerable«

Diese Schnittstelle hat nur die Methode GetEnumerator, die ein Enumerator-Objekt bereitstellt. Ein Enumerator verfügt über die Fähigkeit, eine Auflistung elementweise zu durchlaufen. Damit gleicht dieses Objekt einem Positionszeiger, dem drei Methoden eigen sind: Current, MoveNext und Reset.

Der Enumerator positioniert sich standardmäßig vor dem ersten Eintrag einer Auflistung. Um ihn auf den ersten Eintrag und anschließend auf alle Folgeeinträge zeigen zu lassen, muss die Methode MoveNext ausgeführt werden. Mit Current wird auf den Eintrag zugegriffen, auf den der Enumerator aktuell zeigt. Reset setzt den Enumerator in seine Ausgangsposition zurück, also vor den ersten Eintrag.

Es gibt eine Situation, in der die Fähigkeit des Enumerators ausgesprochen wichtig ist. Es ist die For Each-Schleife, mit der eine Auflistung vom ersten bis zum letzten Element durchlaufen wird:


For Each element In arr
...
Next

Die Schnittstelle »ICollection«

Die Schnittstelle ICollection stattet alle Auflistungen mit weiteren Fähigkeiten aus. Diese Schnittstelle veröffentlicht die in der folgenden Tabelle aufgeführten Eigenschaften und Methoden.


Tabelle 7.2     Mitglieder der Schnittstelle »ICollection«

Eigenschaft/Methode Beschreibung
Count Liefert die Anzahl der Elemente einer Auflistung.
IsSynchronized Liefert einen booleschen Wert, der darüber Auskunft gibt, ob das Auflistungsobjekt synchronisiert, also thread-sicher ist.
SyncRoot Liefert eine Objektreferenz, die den Zugriff auf das angegebene Objekt synchronisiert.
CopyTo Kopiert die Elemente einer Auflistung in ein Array

Insbesondere Auflistungen sind kritisch hinsichtlich des gleichzeitigen Zugriffs mehrerer Threads. Viele Klassen implementieren Thread-Sicherheit durch die Bereitstellung der Methode Synchronized, beispielsweise auch die Klassen ArrayList und Hashtable. Die Eigenschaft IsSynchronized gibt an, ob die Auflistung synchronisiert ist.


Galileo Computing

7.2.2 Die Schnittstelle »IList«  downtop

Auflistungen, die IList implementieren, zeichnen sich dadurch aus, ihre Elemente über Indizes verwalten zu können. Das beste Beispiel hierfür dürfte die Klasse ArrayList sein, aber auch eine große Anzahl weiterer, meist steuerelementspezifischer Auflistungen gehört zu dieser Gruppe.

Wir wollen uns daher zuerst die wichtigsten Eigenschaften und Methoden ansehen, die alle Klassen, die IList implementieren, gemeinsam aufweisen.


Tabelle 7.3     Methoden und Eigenschaften der Schnittstelle »IList«

Methode/Eigenschaft Beschreibung
Item Stellt den Indexer für die IList-Klasse dar und dient dem Zugriff auf ein Listenelement.
Add Hängt ein neues Element an das Ende der Auflistung an.
Clear Löscht alle Elemente der Auflistung.
Contains Gibt zurück, ob ein bestimmtes Objekt bereits zu der Auflistung gehört.
IndexOf Liefert den Index eines bestimmten Objekts.
Insert Fügt ein Objekt an einer bestimmten Position in die Auflistung ein.
IsFixedSize Beschreibt mit einem booleschem Wert, ob die Kapazität der Auflistung dynamisch vergrößert werden kann oder nicht.
IsReadOnly Beschreibt mit einem booleschen Wert, ob die Auflistung schreibgeschützt ist.
Remove Löscht ein Objekt unter der Angabe der Referenz.
RemoveAt Löscht ein Objekt unter der Angabe des Index.

Ihre Stärke spielen Schnittstellen aus, wenn Sie von mehreren Klassen implementiert werden. Jede Klasse weist dann dieselben Merkmale und Verhaltensweisen auf. Wenn Sie mit einer Klasse gearbeitet haben, die eine oder mehrere gängige Schnittstellen unterstützt, sollten Sie auch mit allen anderen Klassen umgehen können, welche die gleichen Schnittstellen unterstützen. Das trifft insbesondere auf die Schnittstelle IList zu, weil sie von sehr vielen Klassen des .NET Frameworks implementiert wird. Es ist daher empfehlenswert, sich insbesondere mit den Eigenschaften und Methoden dieser Schnittstelle vertraut zu machen. Beispiele dazu werden Ihnen im weiteren Verlauf dieses Buchs noch viele begegnen.


Galileo Computing

7.2.3 Die Klasse »ArrayList«  downtop

ArrayList ist die wichtigste Klasse, die das Interface IList implementiert. Ein Objekt vom Typ ArrayList hat standardmäßig eine Kapazität von 16 Einträgen, die sich bei Überschreitung automatisch verdoppelt. Die Kapazität kann beim Erzeugen des Objekts über einen der überladenen Konstruktoren festgelegt werden oder durch Zuweisung an die Eigenschaft Capacity.

Einträge hinzufügen

Mit der aus der Schnittstelle IList geerbten Methode Add können Objekte einer ArrayList-Instanz hinzugefügt werden. Da ArrayList eine 0-basierte Auflistung ist, wird der erste Eintrag den Index 0 haben, der zweite den Index 1 usw. Sie haben mit der Add-Methode keinen Einfluss darauf, an welcher Position das Objekt in der Liste aufgenommen wird, denn es wird an das Listenende gesetzt. Wollen Sie wissen, welchen Index ein hinzugefügtes Objekt erhalten hat, brauchen Sie nur den Rückgabewert der Add-Methode auszuwerten.

Dim liste As New ArrayList
Dim index As Integer = liste.Add(irgendeinObjekt)

Über die von IList übernommene Methode Add hinaus bietet ArrayList mit AddRange eine weitere, typspezifische Methode an, der Sie auch herkömmliche Arrays übergeben können:


Dim liste As New ArrayList
Dim arr() As Integer = {0, 10, 22, 9, 45}
liste.AddRange(arr)

Liegt das Array bereits vor der Instanziierung von ArrayList vor, kann das Array auch direkt dem Konstruktor übergeben werden:


Dim arr() As Integer = {0, 10, 22, 9, 45}
Dim liste As ArrayList = New ArrayList(arr)

Die Add-Methode eines ArrayList-Objekts nimmt Elemente vom Typ Object entgegen. Somit dürfte klar sein, dass von einer ArrayList jeglicher Typus verwaltet werden kann und darüber hinaus auch nicht sichergestellt ist, dass alle Elemente typgleich sind. Das kann einerseits vorteilhaft sein, andererseits besteht aber auch manchmal das Bedürfnis, nur ganz bestimmte Objekte zu speichern. Auf diese Problematik werden wir weiter unten noch genau eingehen und auch Lösungen dazu erarbeiten.

Die Elementverwaltung einer »ArrayList«

Nehmen wir an, mehrere Objekte vom Typ ClassA sollen von einer ArrayList verwaltet werden. ClassA sei wie folgt definiert:


Public Class ClassA
Public Prop As Integer
Public Sub New(ByVal x As Integer)
Prop = x
End Sub
End Class

Im folgenden Code werden die beiden Referenzen obj1 und obj2 vom Typ ClassA der Auflistung col hinzugefügt. Die Auflistung col verwaltet danach obj1 über den Index 0 und obj2 über den Index 1.


Dim obj1 As ClassA = New ClassA(1)
Dim obj2 As ClassA = New ClassA(2)
' Klasse ArrayList instanziieren
Dim col As New ArrayList
' obj1 und obj2 der Auflistung hinzufügen
col.Add(obj1)
col.Add(obj2)

Damit hätten wir aktuell die folgenden Zuordnungen:

gp  Der Listeneintrag col(0) enthält die Referenz obj1.
gp  Der Listeneintrag col(1) enthält die Referenz obj2.

Mit Add haben Sie keinen Einfluss auf die Positionierung der Objekte innerhalb der Liste. Wollen Sie ein Objekt jedoch an einer bestimmten Position einsortieren, sollten Sie anstelle der Add-Methode die Methode Insert benutzen:


Dim obj3 As ClassA = New ClassA(3)
col.Insert(1, obj3)

Das über obj3 referenzierte Objekt wird damit unter dem Index 1 registriert. Falls man versucht, einen Index anzugeben, der größer ist als die Anzahl der Elemente in der Auflistung, kommt es zur Ausnahme ArgumentOutOfRangeException, da der letzte verfügbare Index immer genauso groß ist, wie die Auflistung Elemente enthält.

Was passiert aber mit dem Element obj2, das vorher die Position des Index 1 einnahm? Wir können das prüfen, indem wir eine Methode schreiben, der wir die Referenz auf die Auflistung als Argument übergeben. In der Methode werden die Elemente der übergebenen Auflistung vom ersten bis zum letzten Objekt an der Konsole ausgegeben:


' Auflistung enthält nur typgleiche Einträge
Public Sub GetListElements(ByVal list As IList)
Dim temp As ClassA
For Each temp In list
Console.Write("Collection-Index = {0}", list.IndexOf(temp))
Console.WriteLine(" / Objekt-Nr.{0}", temp.Prop)
Next
End Sub

Der Typ des Parameters ist IList. Wir hätten auch den Typ ArrayList wählen können, halten uns aber mit unserer Festlegung allgemeiner und haben damit eine Methode, die jeden beliebigen Auflistungstyp entgegennimmt, der IList implementiert und ClassA-Objekte verwaltet.

In der Methode GetListElements wird in einer For Each-Schleife die Auflistung vom ersten bis zum letzten Element durchlaufen. Die Laufvariable temp ist vom Typ ClassA deklariert, da wir wissen, dass unsere Auflistung nur Objekte dieses Typs enthält.

Typgleiche Objekte in einer der Auflistungsklassen zu verwalten, ist die Regel. Der Grund dafür ist einleuchtend, denn innerhalb der Schleife wird häufig ein typspezifisches Member des sich aktuell im Zugriff befindlichen Objekts aufgerufen. Verwaltet eine Auflistung unterschiedliche Typen, muss die Laufvariable der Schleife allgemeiner typisiert werden. Innerhalb des Schleifenblocks sind dann eine Typüberprüfung sowie eine Typumwandlung erforderlich, um auf ein spezifisches Merkmal des Objekts zuzugreifen. Das geht natürlich zu Lasten der Performance.


' Auflistung enthält unterschiedliche Typen
Public Sub GetListElements(ByVal list As IList)
Dim temp As Object
For Each temp In list
If TypeOf temp Is ClassA Then
Console.Write("Collection-Index = {0}", _
list.IndexOf(temp))
Console.WriteLine(" / Objekt-Nr.{0}", temp.Prop
End If
Next
End Sub

Sehen wir uns nun die Ausgabe an, die von der Prozedur GetListElements in das Konsolenfenster geschrieben wird, nachdem wir mit Insert ein drittes Objekt an die zweite Listenposition gesetzt haben:


Collection-Index = 0 / Objekt-Nr.1
Collection-Index = 1 / Objekt-Nr.3
Collection-Index = 2 / Objekt-Nr.2

Das ursprünglich dem Index 1 zugeordnete Objekt musste seine Position räumen – es verschiebt sich in der Liste um eine Position in Richtung Listenende, während sich obj3 wunschgemäß einreiht. Hätten wir noch weitere Elemente in unserer Auflistung, würde sich die Indizierung aller Folgeelemente ebenfalls um eine Position verschieben.

Ein ähnliches Verhalten zeigt sich auch, wenn wir mit Remove oder RemoveAt aus der Auflistung einen Eintrag löschen: Der freigegebene Index bleibt nicht unbelegt, sondern bewirkt eine Indexverschiebung aller Nachfolgeelemente. Löschen wir beispielsweise das Element mit dem Index 2, rutscht das Objekt mit dem ursprünglichen Index 3 an die frei gewordene Position 2, das Objekt mit dem Index 4 füllt die Lücke des Index 3 usw. (siehe auch Abbildung 7.2).

Abbildung
Hier klicken, um das Bild zu Vergrößern

Abbildung 7.2     Listenverwaltung beim Löschen

Die Tragweite dieser Elementverwaltung ist weitreichend, denn es kann zu keinem Zeitpunkt die eindeutige Zuordnung eines Objekts zu einem bestimmten Index in der Auflistung garantiert werden.

Auf der Buch-CD finden Sie den Programmcode des Beispiels unter

...\Kapitel 7\ArrayListDemo

Datenaustausch zwischen einem Array und einer »ArrayList«

Auflistungen zeichnen sich durch die beiden Interfaces IEnumerable und ICollection aus. Aus der letztgenannten stammt die Methode CopyTo, die es ermöglicht, die Einträge einer Auflistung in ein Array zu kopieren.


Dim col As New ArrayList
col.Add("Anton")
col.Add("Gustaf")
col.Add("Fritz")
Dim strArr(10) As String
col.CopyTo(strArr, 3)

Der zweite Parameter von CopyTo gibt den Startindex im Array an, ab dem kopiert wird. Das Array muss groß genug sein, um alle Elemente aufzunehmen, sonst wird ein Fehler ausgelöst. Handelt es sich bei den zu kopierenden Einträgen um Objektreferenzen, werden nicht die Objekte, sondern nur die Referenzen kopiert. ArrayList überlädt CopyTo, so dass auch spezifizierte Teilbereiche der Liste kopiert werden können.


Galileo Computing

7.2.4 Das Sortieren der Elemente einer »ArrayList«  downtop

Die von ArrayList verwalteten Objekte sind sortierbar. Das ist keineswegs eine Selbstverständlichkeit, sondern ein wichtiges Charakteristikum dieser Klasse, wie wir später noch im Vergleich mit anderen Auflistungen feststellen werden.

Sortieren mit der Schnittstelle »IComparable«

Um die Mitglieder zu sortieren, wird die Methode Sort auf der ArrayList-Referenz aufgerufen. Sort ist mehrfach überladen. Wir wollen uns zunächst mit der parameterlosen Version beschäftigen:


Public Overridable Sub Sort()

Die Regel, nach der im deutschsprachigen Raum sortiert wird, vergleicht die Zeichen unter Berücksichtigung der Groß- und Kleinschreibung wie folgt:


1 < 2 ... < a < A < b < B < c < C ... < y < Y < z < Z

Um die verwalteten Objekte einer ArrayList mit der parameterlosen Sort-Methode zu sortieren, müssen die Objekte die Schnittstelle IComparable implementieren. Diese Schnittstelle enthält nur die Methode CompareTo. Eine Klasse, die IComparable implementiert, garantiert, die Methode CompareTo zu veröffentlichen. Darauf ist die Sort-Methode der ArrayList angewiesen. Was eine Schnittstellenmethode leisten muss, ist der jeweiligen Dokumentation zu entnehmen. Aus der .NET-Dokumentation zu CompareTo können wir entnehmen, dass das aktuelle Objekt mit dem des Parameters verglichen wird. Als Resultat liefert der Methodenaufruf einen der drei folgenden Werte:

gp  < 0, wenn das aktuelle Objekt »kleiner« als das Objekt obj ist
gp  0, wenn das aktuelle Objekt »gleich« dem Objekt obj ist
gp  > 0, wenn das aktuelle Objekt »größer« als das Objekt obj ist

Die Kriterien, was im Vergleich als »kleiner«, »gleich« und »größer« bewertet wird, muss die Klasse festlegen, welche die Schnittstelle IComparable implementiert. Sehen wir uns dazu ein Beispiel an.


Public Class HoldValue
Implements IComparable
Public IntVar As Integer
Public Sub New(ByVal x As Integer)
IntVar = x
End Sub
Public Function CompareTo(ByVal obj As Object) _
As Integer Implements IComparable.CompareTo
Dim val As HoldValue = obj
If val.IntVar < Me.IntVar Then
Return 1
ElseIf (val.IntVar = Me.IntVar) Then
Return 0
Else
Return –1
End If
End Function
End Class

Die Klasse HoldValue implementiert IComparable. Daher sind Objekte dieses Typs darauf vorbereitet, in einer ArrayList sortiert zu werden. Die Sortierreihenfolge soll sich am Inhalt des Felds intVar orientieren. In der Methode CompareTo wird die dem Parameter übergebene Referenz zuerst in den Typ HoldValue konvertiert und einer lokalen Variablen zugewiesen. Anschließend folgt ein Vergleich zwischen den Feldwerten des aktuellen und des übergebenen Objekts.

Natürlich wollen wir nun auch testen, ob wir unser Ziel erreicht haben. Dazu dient der folgende Testcode:


Sub Main()
Dim arrList As New ArrayList
Dim obj1 As HoldValue = New HoldValue(17)
arrList.Add(obj1)
Dim obj2 As HoldValue = New HoldValue(110)
arrList.Add(obj2)
Dim obj3 As HoldValue = New HoldValue(5)
arrList.Add(obj3)
arrList.Sort()
Dim i As Integer = 0
Dim temp As HoldValue
For Each temp In arrList
Console.WriteLine("Element{0} – Wert: {1}", _
i, temp.IntVar)
i += 1
Next
End Sub

An der Konsole werden die Werte der Felder in der Reihenfolge 5, 17, 100 ausgegeben, obwohl die ursprüngliche Reihenfolge in der Liste eine andere war. Der Vergleich und die anschließende Sortierung findet also wie erwartet statt.

Der Code lässt sich aber auch noch eleganter formulieren. Wenn Sie sich in der .NET-Dokumentation die Definition der Struktur Int32 ansehen, werden Sie feststellen, dass dieser Typ seinerseits selbst die Schnittstelle IComparable implementiert. Es ist daher nahe liegend, den Vergleich am Feld intVar direkt vorzunehmen:


Public Function CompareTo(ByVal obj As Object) _
As Integer Implements Comparable.CompareTo
Dim val As HoldValue = obj
Return Me.IntVar.CompareTo(val.IntVar)
End Function

Damit können wir uns aber noch nicht ganz zufrieden geben, denn alle denkbaren Szenarien werden von unserer Implementierung noch nicht berücksichtigt. Es könnte nämlich auch ein Objekt übergeben werden, das mit dem aktuellen nicht vergleichbar ist, beispielsweise:


Dim kreis As Circle = New Circle(5)
Dim val As HoldValue = New HoldValue(8)
Dim x As Integer = val.CompareTo(kreis)

Wenn Sie die Methode CompareTo implementieren, sollten Sie diesem Fall ebenso berücksichtigen wie die Eventualität, dass das übergebene Objekt noch nicht initialisiert und daher Nothing ist. Die Implementierung, die diese beide Szenarien einbezieht, sieht wie folgt aus:


Public Function CompareTo(ByVal obj As Object) _
As Integer Implements IComparable.CompareTo
' Prüfen, ob der Parameter ein Nothing-Verweis ist
If obj Is Nothing Then
Return 1
End If
' Prüfen, ob beide Typen gleich sind
If (Not TypeOf obj Is HoldValue) Then
Throw New ArgumentException("Ungültiger Vergleich")
End If
' Vergleich der beiden Objekte
Dim val As HoldValue = obj
Return Me.IntVar.CompareTo(val.IntVar)
End Function

Generell sollten Sie die Methode CompareTo der Schnittstelle IComparable wie gezeigt implementieren, um gegen alle unzulässigen Aufrufe gewappnet zu sein. Es wird zuerst überprüft, ob dem Parameter Nothing übergeben wurde. Der Vergleich sollte daraufhin abgebrochen werden und als Resultat einen Wert größer 0 liefern. Damit wird ein Nothing-Verweis vor einem Objektverweis einsortiert. Unterscheiden sich die beiden Typen des anstehenden Vergleichs, wird die Ausnahme ArgumentException ausgelöst und muss vom Aufrufer behandelt werden.

Auf der Buch-CD finden Sie den Programmcode des Beispiels unter:

...\Kapitel 7\IComparableDemo

Vergleichsklassen mit »IComparer«

Das Sortieren einer ArrayList mit der parameterlosen Sort-Methode gestattet nur ein Vergleichskriterium. Manchmal ist es aber erforderlich, unterschiedliche Sortierkriterien zu berücksichtigen. Nehmen wir zum Beispiel die Klasse Person, welche die beiden Felder Name und Wohnort beschreibt.


Class Person
Public Name As String
Public Wohnort As String
Public Sub New(ByVal name As String, ByVal ort As String)
name = name
Wohnort = ort
End Sub
End Class

Würden die Klassen die Schnittstelle IComparable implementieren, müsste die Entscheidung getroffen werden, nach welchem Feld Objekte dieser Klasse sortiert werden können. Nun sollen beide Möglichkeiten angeboten werden.

Die Lösung des Problems führt über die Bereitstellung so genannter Vergleichsklassen, welche die Schnittstelle IComparer implementieren. In jeder Vergleichsklasse wird genau ein Vergleichskriterium festgelegt. Wollen wir einen bestimmten Objektvergleich erzwingen, müssen wir der Sort-Methode mitteilen, welche Vergleichsklasse dafür bestimmt ist. Dafür stehen uns zwei Überladungen zur Verfügung, denen die Referenz auf ein Objekt übergeben wird, das die Schnittstelle IComparer implementiert:


Public Overridable Sub Sort(IComparer)
Public Overridable Sub Sort(Integer, Integer, IComparer)

Mit der Überladung, die zwei Integer erwartet, können der Startindex und die Länge des zu sortierenden Bereichs bestimmt werden. Bei sehr großen Auflistungen steigert das die Performance, da Sortiervorgänge sehr rechenintensiv sind.

Die Schnittstelle IComparer stellt eine Methode für den Vergleich zweier Objekte bereit:


Function Compare(x As Object, y As Object) As Integer

Compare funktioniert ähnlich der weiter oben erörterten Methode CompareTo und gibt die folgenden Werte zurück:

gp  < 0, wenn das erste Objekt »kleiner« als das zweite Objekt ist
gp  0, wenn das erste Objekt »gleich« dem zweiten Objekt ist
gp  > 0, wenn das erste Objekt »größer« als das zweite Objekt ist

Hinweis

Der große Unterschied zwischen den beiden Schnittstellenmethoden IComparable.CompareTo und IComparer.Compare ist die Parameterliste und der daraus resultierende Methodenaufruf. CompareTo nimmt eine Referenz entgegen, die mit dem aktuellen Objekt verglichen wird, während Compare zwei zu vergleichende Referenzen übergeben werden. Somit ist diese Methode auch unabhängig von der Me-Referenz. Die Schnittstelle IComparer bietet sich daher auch an, wenn Sie die Objekte eines Typs vergleichen wollen, der nicht IComparable implementiert.


Für die Klasse Person wollen wir nun die beiden Vergleichsklassen NameComparer und WohnortComparer entwickeln, die gemäß Forderung die Schnittstelle IComparer implementieren und nach Wohnort bzw. Name sortieren.


' Vergleichsklasse – Kriterium 'Wohnort'
Class WohnortComparer
Implements IComparer
Public Function Compare(ByVal x As Object, ByVal y As Object) _
As Integer Implements IComparer.Compare
' Prüfen auf Nothing-Übergabe
If (x Is Nothing And y Is Nothing) Then
Return 0
End If
If (x Is Nothing) Then
Return 1
End If
If (y Is Nothing) Then
Return –1
End If
' Typüberprüfung
If (Not x.GetType Is y.GetType) Then
Throw New ArgumentException("Ungültiger Vergleich")
End If
' Vergleich
Return x.Wohnort.CompareTo(y.wohnort)
End Function
End Class
' Vergleichsklasse – Kriterium 'Name'
Class NameComparer
Implements IComparer
Public Function Compare(ByVal x As Object, ByVal y As Object) _
As Integer Implements IComparer.Compare
' Prüfen auf Nothing-Übergabe
If (x Is Nothing And y Is Nothing) Then
Return 0
End If
If (x Is Nothing) Then
Return 1
End If
If (y Is Nothing) Then
Return –1
End If
' Typüberprüfung
If (Not x.GetType Is y.GetType) Then
Throw New ArgumentException("Ungültiger Vergleich")
End If
' Vergleich
Return x.Zuname.CompareTo(y.Zuname)
End Function
End Class

Die Implementierung ähnelt der der Methode CompareTo. Zuerst sollte wieder ein Vergleich mit Nothing durchgeführt werden und anschließend eine Prüfung, ob beide Parameter denselben Typ beschreiben oder zumindest einen vergleichbaren Typ besitzen. Sollte keine Bedingung zutreffen, kann der Vergleich der Objekte erfolgen. Dabei unterstützt uns die Klasse String, die ihrerseits die IComparable-Schnittstelle implementiert, mit der Methode CompareTo.

Haben wir ein ArrayList-Objekt mit Person-Objekten gefüllt, steht es uns frei, welche Vergleichsklasse wir zur Sortierung der Objekte benutzen, denn beide sind auf dieselbe Schnittstelle zurückzuführen und gegenseitig austauschbar.


' ----------------------------------------------------------
' Beispiel: ...\Kapitel 7\IComparerDemo
' ----------------------------------------------------------
Module Module1
Sub Main()
Dim arrList As New ArrayList
' ArrayList füllen
Dim pers1 As Person = New Person("Meier", "Berlin")
arrList.Add(pers1)
Dim pers2 As Person = New Person("Arnhold", "Köln")
arrList.Add(pers2)
Dim pers3 As Person = New Person("Graubär", "Aachen")
arrList.Add(pers3)
' nach Wohnorten sortieren
arrList.Sort(New WohnortComparer())
Console.WriteLine("Liste nach Wohnorten sortiert")
ShowSortedList(arrList)
' nach Namen sortieren
arrList.Sort(New NameComparer())
Console.WriteLine("Liste nach Namen sortiert")
ShowSortedList(arrList)
Console.ReadLine()
End Sub
Sub ShowSortedList(ByVal liste As IList)
Dim temp As Person
For Each temp In liste
Console.Write("Name = {0,-12}", temp.Zuname)
Console.WriteLine("Wohnort = {0}", temp.Wohnort)
Next
Console.WriteLine()
End Sub
End Module

Die statische Methode »ArrayList.Adapter«

Mit der statischen Methode Adapter kann ein Wrapper (darunter ist eine Klasse zu verstehen, die eine andere einhüllt) um ein IList-Objekt gelegt werden. Der Rückgabewert ist die Referenz auf ein neues ArrayList-Objekt, auf dessen Methoden sich das IList-Objekt manipulieren lässt.


Public Shared Function Adapter(IList)As ArrayList

Wie Sie die Methode Adapter einsetzen können, möchte ich Ihnen an einem Beispiel zeigen. Wie Sie der .NET-Dokumentation zu IList entnehmen können, implementiert auch ein gewöhnliches Array diese Schnittstelle. Ein Array kann allerdings nicht sortiert werden. Über den Aufruf von Adapter wird das allerdings möglich.

Nehmen wir an, die Klasse Person sei wie folgt definiert:


Public Class Person
Public Zuname As String
Public Alter As Integer
Public Sub New(ByVal name As String, ByVal alt As Integer)
Zuname = name
Alter = alt
End Sub
End Class

Ein Array vom Typ Person soll mehrere Objekte enthalten, die dem Namen nach sortiert werden sollen. Dazu rufen wir die statische Methode Adapter unter Übergabe des Arrays auf und weisen die zurückgelieferte Referenz der Methode einer ArrayList-Variablen zu.


Dim pers(2) As Person
pers(0) = New Person("Peter", 15)
pers(1) = New Person("Alfred", 33)
pers(2) = New Person("Hugo", 26)
Dim liste As ArrayList = ArrayList.Adapter(pers)

Die Elemente des Arrays sollen jetzt dem Namen nach sortiert werden. Dazu bietet sich eine Überladung der Methode Sort der ArrayList an:


Public Overridable Sub Sort(IComparer)

Da ein Array die Schnittstelle IComparer nicht implementiert, die notwendig wäre, um diese Überladung aufzurufen, müssen wir eine Vergleichsklasse, welche die Schnittstelle IComparer implementiert, bereitstellen:


Public Class SortByName
Implements IComparer
Public Function Compare(ByVal x As Object, _
ByVal y As Object) As Integer Implements IComparer.Compare
Return x.Zuname.CompareTo(y.Zuname)
End Function
End Class

Mit dem Aufruf von Sort unter Übergabe eines Objekts vom Typ der Vergleichsklasse SortByName können wir über den bereitgestellten Wrapper die Elemente des Arrays pers in die gewünschte Reihenfolge bringen:


liste.Sort(new SortByName())

Den vollständigen Programmcode zu diesem Beispiel finden Sie auf der Buch-CD unter:

.\Kapitel 7\ArrayList_Adapter)


Galileo Computing

7.2.5 Die Schnittstelle »IDictionary«  downtop

IList-Auflistungen verwalten die Objekte über Indizes. Dieses Konzept hat aber einen Nachteil: Wenn man nach einem bestimmten Element sucht und dessen Position nicht kennt, muss man die Liste so lange durchlaufen, bis man eine Übereinstimmung findet. Enthält die Auflistung sehr viele Einträge, kann das sehr zeitaufwändig sein und kostet Rechenleistung.

Kommt es nicht auf die Reihenfolge der Elemente an, kann man sich für eine Auflistung, die das Interface IDictionary implementiert, entscheiden. Dazu gehört die Klasse Hashtable, die unten vorgestellt wird. In diesen Auflistungen kann ein bestimmtes Element zwar schneller gefunden werden, allerdings muss man dabei in Kauf nehmen, keinen Einfluss auf die Positionierung der Elemente in der Liste zu haben. IDictionary-Collections organisieren die Elemente in einer für sie passenden Reihenfolge.

Um nach einem Element in einer IDictionary-Auflistung zu suchen, wird eine Schlüsselinformation benötigt, der ein Wert zugeordnet ist. IDictionary-Auflistungen enthalten Elemente mit Schlüssel-Wert-Kombinationen. Der Schlüssel muss eindeutig sein und darf nicht den Inhalt Nothing haben. In einer IList-Collection entspricht der Schlüssel dem Index. Der wesentliche Unterschied ist dabei jedoch, dass der Schlüssel nicht garantiert, eindeutig einem bestimmten Eintrag zugeordnet zu sein.

Stellen Sie sich dazu vor Sie beabsichtigten, die Mitarbeiter eines Unternehmens in einer Auflistung zu verwalten. Jeder Mitarbeiter ist über eine eindeutige Personalnummer identifizierbar. Diese Personalnummer beschreibt gleichzeitig, welche persönlichen Daten zu dem Mitarbeiter gehören:


0999–123–3 = Franz Fischer
0100–288–3 = Peter Müller
6771–771–1 = Marita Kohl

Hinter jeder Personalnummer verbirgt sich genau ein Mitarbeiter, aber einem Mitarbeiter könnten durchaus auch zwei Personalnummern zugewiesen werden – vielleicht weil er zwei separat honorierte Positionen besetzt. Diese drei Wertpaare ließen sich problemlos durch eine IDictionary-Auflistung abbilden. Der Schlüssel würde durch die Personalnummer beschrieben, der Name entspräche dem Wert. In der Realität würde man dann allerdings wahrscheinlich sinnvollerweise den Namen durch eine Objektreferenz ersetzen, die auf das dem Mitarbeiter zugeordnete Objekt verweist. Der Schlüssel kann durchaus selbst ein Objekt sein, wird aber häufig durch Zeichenfolgen beschrieben.

Methoden und Eigenschaften der Schnittstelle »IDictionary«

Die meisten der von IDictionary veröffentlichten Methoden sind uns bereits aus der Schnittstelle IList bekannt. Das erleichtert zwar einerseits die Einarbeitung, zwingt uns aber andererseits dennoch in einigen Fällen zu einer etwas genaueren Betrachtung.

Jeder Listeneintrag in einer IDictionary-Auflistung wird durch ein Schlüssel-Wert-Paar beschrieben, was sich in der Parameterliste der Add-Methode niederschlägt:


Sub Add(key As Object, value As Object)

Der erste Parameter wird als Schlüssel für das hinzuzufügende Element verwendet und sorgt für die Identifizierbarkeit innerhalb einer Liste, der zweite ist die Referenz auf das hinzuzufügende Element. Wir stoßen hier zum ersten Mal auf die Tatsache, dass von IDictionary-Auflistungen anstelle eines Indizes ein Schlüssel verwendet wird.

Der Schlüssel begleitet uns durch alle Methoden und wird auch von Remove zum Entfernen eines Objekts aus der Auflistung verwendet:


Sub Remove(key As Object)

Da IDictionary-Objekte nicht über Indizes verwaltet werden, brauchen nach dem Löschen eines Elements etwaige Folgeelemente auch keine Lücke zu schließen.

Gibt man einen Schlüssel an, der sich noch nicht in der Auflistung befindet, wird das Element hinzugefügt. Dabei bleibt der Wert leer, ist also Nothing, was durchaus zulässig ist.

Die Schlüssel und die Werte werden in eigenen Auflistungen verwaltet. Die Referenz auf diese internen Auflistungen liefern die Eigenschaften Keys und Values.


ReadOnly Property Keys As ICollection
ReadOnly Property Values As ICollection

Mit Clear kann eine IDictionary-Auflistung geleert werden, mit Contains können wir prüfen, ob ein bestimmter Schlüssel bereits in der Liste enthalten ist.


Tabelle 7.4     Eigenschaften und Methoden des Interface »IDictionary«

Eigenschaft/Methode Beschreibung
Add Hinzufügen eines Objekts zur Auflistung.
Remove Löschen eines Elements aus der Auflistung.
Item Zugriff auf ein Element der Auflistung.
Keys Liefert alle in der Liste verwendeten Schlüssel zurück.
Values Liefert alle in der Liste verwendeten Werte zurück.
Clear Löscht alle Elemente der Auflistung.
Contains Prüft, ob ein bestimmter Schlüssel in der Auflistung enthalten ist.


Galileo Computing

7.2.6 Die Klasse »Hashtable«  downtop

Die wichtigste Auflistung, die das IDictionary-Interface implementiert, wird von der Klasse Hashtable beschrieben. Dieser Auflistungstyp ist eine Datenstruktur, die ein schnelles Suchen nach Objekten erlaubt. Der Name rührt daher, dass für die Verwaltung der Elemente ein Hash-Wert für den Schlüssel verwendet wird. Zum Erzeugen des Hash-Werts wird intern die von Object geerbte Methode GetHashCode ausgeführt.

Im folgenden Beispiel wird eine Hashtabelle erzeugt, die vier Objekte vom Typ ClassA sowie eine Zeichenfolge verwaltet. Damit wir sehen, wie wir über den Tabelleneintrag auf das Member eines registrierten Elements zugreifen, ist in der Definition der ClassA die öffentliche Eigenschaft IntVar deklariert, der wir über den Konstruktor einen Wert übergeben. Im Beispielcode werden die wichtigsten Methoden einer Hashtabelle benutzt, um Informationen sowohl über die Elemente als auch über die Listeneinträge zu erhalten.


' ----------------------------------------------------------
' Beispiel: ...\Kapitel 7\Hashtabelle
' ----------------------------------------------------------
Module Module1
Dim obj1 As ClassA = New ClassA(1)
Dim obj2 As ClassA = New ClassA(2)
Dim obj3 As ClassA = New ClassA(3)
Dim obj4 As ClassA = New ClassA(4)
Dim hash As Hashtable
Sub Main()
hash = New Hashtable()
' Objekte der Hashtabelle hinzufügen
AddObjects()
' Liste der Schlüssel ausgeben
GetKeyList()
' Liste der Werte ausgeben
GetValueList()
' Liste der Schlüssel und Werte ausgeben
GetCompleteList()
Console.WriteLine()
' Zugriff auf ein bestimmtes Element
Console.Write("Geben Sie nun den Schlüssel ")
Console.Write("des Objekts ein, dessen Eigenschaft ")
Console.Write("intVar Sie auswerten wollen: ")
Dim input As String = Console.ReadLine()
' prüfen, ob der Schlüssel sich in der Hashtabelle befindet
If (hash.Contains(input)) Then
Console.Write("Das Objekt {0} ", input)
Console.Write("hat in intVar den Inhalt {0}", _
hash(input).IntVar)
Console.WriteLine()
Else
Console.WriteLine("Nicht Element der Hashtabelle")
End If
' anhand des Wertes prüfen, ob sich ein Objekt
' bereits in der Hashtabelle befindet
Console.Write("Aufruf von ContainsValue: ")
If (hash.ContainsValue(obj2)) Then
Console.WriteLine("Das Objekt ist enthalten.")
Else
Console.WriteLine("Das Objekt ist nicht enthalten.")
End If
Console.ReadLine()
End Sub
' Ausgabe der Wertliste
Public Sub GetValueList()
Console.WriteLine()
Console.WriteLine("===== Werteliste =====")
Dim obj As Object
For Each obj In hash.Values
Console.WriteLine(obj)
Next
End Sub
' Ausgabe der Schlüsselliste
Public Sub GetKeyList()
Console.WriteLine()
Console.WriteLine("===== Schlüsselliste =====")
Dim obj As Object
For Each obj In hash.Keys
Console.WriteLine(obj)
Next
End Sub
' Schlüssel-Wert-Paar über ein DictionaryEntry-Objekt ausgeben
Public Sub GetCompleteList()
Console.WriteLine()
Console.WriteLine("===== Schlüssel-/Wertepaare =====")
Dim dicEntry As DictionaryEntry
For Each dicEntry In hash
Console.Write(dicEntry.Key)
Console.WriteLine(" – {0}", dicEntry.Value)
Next
End Sub
' Objekte der Hashtabelle hinzufügen
Public Sub AddObjects()
hash.Add("eins", obj1)
hash.Add("zwei", obj2)
hash.Add("drei", obj3)
hash.Add("vier", obj4)
hash.Add("fünf", "Hallo")
End Sub
End Module
Class ClassA
Public IntVar As Integer
Public Sub New(ByVal x As Integer)
IntVar = x
End Sub
End Class

Das Objekt hash vom Typ Hashtable wird mit dem parameterlosen Konstruktor erzeugt, der meistens ausreichen dürfte. Anschließend werden in der benutzerdefinierten Methode AddObjects vier ClassA-Objekte in der Hashtabelle registriert. Darüber hinaus wird auch noch eine Zeichenfolge als fünftes Objekt eingetragen. Dem Aufruf der Methode Add werden dazu Schlüssel und Wert übergeben. Im Beispiel ist der Schlüssel eine Zeichenfolge, die Objektreferenz stellt den Wert dar. Ist ein Schlüssel bereits in der Hashtabelle enthalten, kommt es zur Auslösung der Ausnahme ArgumentException.

»DictionaryEntry« zur Auswertung der Schlüssel oder Werte

Der Inhalt einer Hashtabelle lässt sich abfragen – sowohl die Liste der Schlüssel als auch die Liste der Werte. Dazu dienen die Eigenschaften Keys und Values. In den Methoden GetKeyList und GetValueList wird in jeweils einer For Each-Schleife die Werte- bzw. Schlüsselliste durchlaufen. Beachten Sie, dass die Laufvariablen der Schleifen nicht dazu benutzt werden können, auf das Listenelement zuzugreifen, um in unserem Fall beispielsweise das Feld IntVar auszuwerten.

Um auf ein Listenelement in einer For Each-Schleife zugreifen zu können, müssen Sie die Laufvariable vom Typ DictionaryEntry deklarieren. Tatsächlich sind die Elemente in einer HashTable von diesem Typ. DictionaryEntry ist eine Struktur, die das Schlüssel-Wert-Paar für einen Hashtabelleneintrag enthält. Über die Eigenschaften Key und Value können wir die notwendigen Informationen beziehen. Während uns Key nur den Schlüssel liefert, können wir über den Rückgabewert von Value auf das Objekt zugreifen:


Dim dicEntry As DictionaryEntry
For Each dicEntry In hash
Console.Write(dicEntry.Key)
Console.WriteLine(" – {0}", dicEntry.Value)
Next


Hinweis

Dass die Einträge in einer Hashtabelle vom Typ DictionaryEntry sind, müssen Sie berücksichtigen, wenn Sie mit der Methode CopyTo die Einträge in ein Array kopieren wollen. Das Array muss dann vom diesem Typ oder vom Typ Object sein.


Prüfen, ob ein Element bereits zur »Hashtable« gehört

Eine HashTable dient zur Verwaltung mehrerer meist gleichartiger Objekte und hat im Vergleich zu anderen Auflistungen den Vorteil, einen sehr schnellen Zugriff über den Indexer zu ermöglichen. Im Beispiel oben wird der Benutzer an der Konsole dazu aufgefordert, einen Schlüssel anzugeben, nach dem in der Hashtabelle gesucht werden soll. Ob der Schlüssel einem Element der Auflistung zugeordnet werden kann, wird durch die Methode Contains festgestellt, die einen booleschen Wert zurückliefert:


If hash.ContainsValue(input) Then

Analog könnte man auch die Methode ContainsKey benutzen, die sich in keiner Weise von Contains unterscheidet.

Nicht nur über den Schlüssel lässt sich prüfen, ob ein Element Mitglied der Hashtabelle ist. Auch über den booleschen Rückgabewert von ContainsValue ist das möglich. Im Beispiel wird dazu direkt die Referenz obj2 übergeben, die natürlich immer zu derselben Konsolenausgabe führt:


If hash.ContainsValue(obj2) Then ...


Galileo Computing

7.2.7 Die Klassen »Queue« und »Stack«  downtop

Weder die Klasse Queue noch die Klasse Stack implementiert das Interface IList oder IDictionary. Dennoch werden beide den Auflistungen zugerechnet, weil sie die Schnittstellen ICollection und somit auch IEnumerable implementieren.

Stack ist eine Datenstruktur, die nach dem LIFO-Prinzip (Last-in-First out) arbeitet: Das Element, das als letztes eingefügt wurde, wird beim folgenden Lesevorgang wieder entnommen. Daraus folgt, dass man auf das Element, das als erstes auf den Stack gelegt worden ist, erst dann wieder zugreifen kann, wenn alle anderen Elemente den Stack verlassen haben.

Ein Queue-Objekt ist das Pendant zu Stack. Es arbeitet nach dem FIFO-Prinzip (First-in-First out), das besagt, dass das zuerst in die Queue geschobene Element auch als erstes wieder entnommen wird. Das Prinzip gleicht also einer Warteschlange an der Kasse eines Fußballstadions.

Die »Stack«-Klasse

Schauen wir uns in einem Beispiel an, wie man mit der Klasse Stack arbeitet.


' ----------------------------------------------------------
' Beispiel: ...\Kapitel 7\StackClass
' ----------------------------------------------------------
Module Module1
Sub Main()
Dim myStack As Stack = New Stack(10)
' Stack füllen
For i As Integer= 0 To 10
myStack.Push(i * i)
Next
' Ausgabe an der Konsole
PrintStack(myStack)
Console.ReadLine()
End Sub
Public Sub PrintStack(ByVal obj As Stack)
' alle Elemente aus dem Stack holen
Do While (obj.Count <> 0)
Console.WriteLine(obj.Pop())
Loop
End Sub
End Module

Das Hinzufügen neuer Elemente geschieht durch den Aufruf der Methode Push, die als Argument ein Objekt erwartet. Im Beispielcode wird eine Schleife durchlaufen, in der insgesamt elf Zahlen auf den Stack gelegt werden. Es handelt sich dabei immer um das Quadrat des aktuellen Schleifenzählers.

Zugegriffen werden kann nur auf das oberste Element im Stack. Dabei handelt es sich immer um das Objekt, das als letztes mit der Push-Methode auf den Stack gelegt wurde.

Es bieten sich zwei Alternativen an, das oberste Element auszuwerten: Mit Pop wird das oberste Element nicht nur zurückgeliefert, sondern gleichzeitig auch der Stack-Verwaltung entzogen. Mit Peek erhält man zwar die Referenz, ohne es jedoch gleichzeitig zu entfernen. Im Beispiel wird der Stack so lange mit Pop abgegriffen, bis die Liste wieder leer ist. Die Reihenfolge der Zahlen beim Hinzufügen lautete:


0 1 4 9 16 25 36 ... 81 100

Die Rückgabe erfolgt mit:


100 81 64 ... 25 16 9 4 1 0

Der Aufruf des parameterlosen Konstruktors der Klasse Stack führt zu einer Standardkapazität von 32 Elementen, die bei Bedarf automatisch erhöht wird, um weitere Elemente aufzunehmen. Dabei werden alle Elemente in ein neues Array kopiert. Wenn Sie wissen, dass Sie diese Anzahl überschreiten werden, sollten Sie aus Gründen einer besseren Performance den parametrisierten Konstruktor wählen, der die Übergabe der erforderlichen Startkapazität ermöglicht:


Dim stack As Stack = New Stack(100)

Reicht das immer noch nicht aus und wird zur Laufzeit die Initialisierungsgröße trotzdem überschritten, verdoppelt sich die Kapazität automatisch.

Die Klasse »Queue«

Das Beispiel, das vorhin die Klasse Stack veranschaulichte, wird nun auf ein Queue-Objekt umgeschrieben:


' ----------------------------------------------------------
' Beispiel: ...\Kapitel 7\QueueClass
' ----------------------------------------------------------
Module Module1
Sub Main()
Dim myQueue As New Queue
' Queue füllen
Dim i As Integer
For i = 0 To 10
myQueue.Enqueue(i * i)
Next
' Ausgabe an der Konsole
PrintStack(myQueue)
Console.ReadLine()
End Sub
Public Sub PrintStack(ByVal obj As Queue)
' alle Elemente aus dem Stack holen
Do While (obj.Count <> 0)
Console.WriteLine(obj.Dequeue())
Loop
End Sub
End Module

Diesmal sind es die beiden Methoden Enqueue und Dequeue, mit denen Elemente in die Liste geschoben und wieder aus ihr geholt werden. Dequeue liefert nicht nur die Referenz des sich am Anfang befindlichen Elements, es holt dieses Element auch aus der Warteschlange. Wie bei der Klasse Stack können Sie sich mit Peek auch die Referenz dieses Elements besorgen und es gleichzeitig in der Liste lassen.

Der Elementzugriff erfolgt in derselben Reihenfolge, in der die Objekte der Liste hinzugefügt wurden: Das erste hinzugefügte Element wird auch als erstes herausgeholt, danach kann man das zweite in die Warteschlange gelegte holen usw. Ein Zugriff auf ein beliebiges Element ist weder beim Stack noch bei der Queue möglich.

Die Standardkapazität eines Queue-Objekts beträgt 32 Elemente, die Sie mittels eines anderen Konstruktors bei der Instanziierung bedarfsgerecht festlegen können.


Galileo Computing

7.2.8 Objektauflistungen im Überblick  downtop

Mit ArrayList, Hashtable, Queue und Stack haben Sie bereits die wichtigsten Auflistungsklassen kennen gelernt. Die .NET-Klassenbibliothek stellt darüber hinaus noch weitere, auf spezifische Anwendungsfälle optimierte Auflistungen bereit, von denen die meisten im Namespace System.Collections.Specialized zu finden sind.


Hinweis

Genau genommen unterschlage ich Ihnen an dieser Stelle eine ganz neue Gruppe von Auflistungen, die seit dem .NET Framework 2.0 verfügbar sind; denn die Auflistungen unterteilen sich in zwei Gruppen:

untypisierte Auflistungen typisierte Auflistungen (generische Auflistungen)

Generische Auflistungen wurden mit .NET Framework 2.0 eingeführt und bieten gegenüber den untypisierten den Vorteil, dass sie bereits zur Entwicklungszeit auf einen bestimmten Typ geprägt werden können, d. h., es wird ein ganz bestimmter Typ verwaltet. Generische Auflistungen finden Sie im Namespace System.Collections.Generic. Mit generischen Typen befassen wir uns im nächsten Abschnitt.


In der folgenden Tabelle erhalten Sie einen Überblick über die Auflistungsklassen, mit denen wir uns nicht näher beschäftigt haben. Da wir uns bereits einige typische Auflistungen genauer angesehen haben, ist es sicherlich nicht mehr schwierig, sich im Bedarfsfall in die Fähigkeiten einer anderen einzuarbeiten. Letztendlich finden wir immer die gleichen Eigenschaften und Methoden vor, die sich meist nur in der Parameterliste unterscheiden.


Tabelle 7.5     Weitere Auflistungsklassen des .NET Frameworks

Klasse Beschreibung
BitArray Verwaltet einen Array von Bits.
CollectionsUtil Eine Auflistung, bei der keine Unterscheidung zwischen Groß- und Kleinschreibung erfolgt.
HybridDictionary Das Verhalten orientiert sich an der Anzahl der Listenelemente. Ist die Anzahl der Elemente gering, operiert diese Klasse als ListDictionary-Collection, wird die Anzahl größer, als Hashtable.
ListDictionary Solange die Anzahl der Elemente kleiner 10 ist, werden die Operationen mit den Elementen schneller ausgeführt als bei einer Hashtable.
NameValueCollection Verwaltet ein Schlüssel-Wert-Paar, wobei sowohl der Schlüssel als auch der Wert durch Zeichenfolgen beschrieben werden. Einem Schlüssel können mehrere Zeichenfolgen zugeordnet werden, d. h., der Schlüssel ist nicht eindeutig.
SortedList Diese Auflistung verwaltet Schlüssel-Wert-Paare, die nach den Schlüsseln sortiert sind und auf die sowohl über Schlüssel als auch über Indizes zugegriffen werden kann. Damit vereint sie die Merkmale von Hashtable und ArrayList.
StringCollection Eine Auflistung, die nur Zeichenfolgen enthält.
StringDictionary Ähnlich einer Hashtable, der Schlüssel ist jedoch immer eine Zeichenfolge.

Welche Auflistung entspricht meinen Anforderungen?

Im Einzelfall kann es sich als schwierig erweisen, aus der großen Anzahl der angebotenen Typen die für die aktuellen Anforderungen am besten geeignete zu wählen. Im Ausschlussverfahren sollten Sie sich dem Typ nähern, der Ihnen unter den gegebenen Umständen die maximale Performance und das gewünschte Verhalten bietet.

Wissen Sie, dass der wahlfreie, also beliebige Zugriff auf die Listenelemente erforderlich ist, verabschieden sich bereits die ersten beiden Klassen aus dem Angebot (Stack und Queue). Das nächste Kriterium auf dem Weg zur Entscheidungsfindung dürfte die Antwort auf die Frage sein, ob die Verwaltung über einen Index gewünscht oder sogar gefordert wird. Das könnte beispielsweise der Fall sein, wenn in einer Schleife über einen Schleifenzähler die Listenelemente der Reihe nach besucht werden müssen. Die Wahl würde in diesem Fall ArrayList oder SortedList lauten.

Objekte, die sich durch eine Schlüssel-Wert-Kombination beschreiben lassen, werden meist in Auflistungen verwaltet, die nicht indexbasiert sind. Ist die Anzahl der Elemente sehr klein, würde sich der Typ ListDictionary anbieten, ist die Anzahl größer, eignet sich besser Hashtable. Falls Sie keinen Mut zur Entscheidung haben – mit HybridDictionary geben Sie die Verantwortung ab. Liegt eine Schlüssel-Wert-Kombination vor und können Sie dennoch nicht auf die Elementsortierung in der Liste verzichten, lautet die Entscheidung wieder SortedList.

In speziellen Sonderfällen wird man auch noch einen Blick auf andere Typen werfen müssen, aber mit den eben erwähnten sind sicherlich 95  % aller Anwendungsfälle abzudecken.


Galileo Computing

7.2.9 Benutzerdefinierte Auflistungen  toptop

Sie suchen eine Auflistungsklasse, die ausschließlich bestimmte Typen verwaltet? Möglicherweise sogar Objekte eines benutzerdefinierten Typs? Sie werden mit Sicherheit keine passende Klasse mit der geforderten strikten Typbindung im .NET Framework finden. Sie haben jetzt zwei Alternativen:

gp  Sie leiten eine passende, vom .NET Framework zur Verfügung gestellte Klasse ab, z.  B. CollectionBase.
gp  Sie entwickeln eine generische Auflistungsklasse oder benutzen eine aus der .NET-Klassenbibliothek.

Ich werde Ihnen zuerst zeigen, wie Sie eine eigene Auflistungsklasse durch Ableitung bereitstellen. In Abschnitt 7.3 werden wir uns mit den Generics auseinander setzen, die einen anderen, sicherlich auch einfacheren Weg aufzeigen. Nichtsdestotrotz halte ich es für sehr lehrreich, sich den Weg über die Ableitung anzusehen. Lassen Sie uns deshalb damit jetzt starten.

Die Ableitung der Klasse »CollectionBase«

Angenommen wir hätten eine Klasse namens HoldValue entwickelt und wollen viele Objekte dieses Typs von einer Auflistung verwalten lassen. Ein erster Ansatz könnte sein, eine neue Klasse bereitzustellen, die ein Objekt vom Typ ArrayList aggregiert. Von unserer Auflistungsklasse werden Methoden und Eigenschaften veröffentlicht, die es ermöglichen, durch Weiterleitung die Methoden und Eigenschaften des internen ArrayList-Objekts zu bedienen.


Class HoldValue
Public Value As Integer
Public Sub New(ByVal val As Integer)
value = value </